6.2 channel
在并发编程中,共享变量一直都是一个大问题,也是经常容易出问题的地方。
那么在Go
语言中就提出了channel
,也就是通道的概念,用以解决协程间的通信问题。
本节我们将对channel
进行讲解。
本节代码存放目录为 lesson17
什么是cahnnel
channel
我们可以将其理解为通道,那么就说明它是一个:一端进一端出的概念。
那么在并发编程中,就可以用来连接多个协程。比如说一个协程往通道中写数据,一个协程从通道的另一端读数据。
其实也就是一个类似队列的东西,这样就可以保证数据不会冲突,我们可以将需要处理的业务、数据等通过通道发送,再由多个协程来进行消耗。
使用channel
channel
分为有缓冲与无缓冲两种。
有缓冲的通道也就是指:这个通道是可以放多个数据的,就算读取端还没有读取,直到放满为止。
无缓冲的通道指:这个通道只可以放一个数据,只有当读取端将这个数据读取完成之后,发送端才可以继续发送数据,否则就是阻塞的。
channel
的使用也比较简单,我们可以结合上一节所学习到的goroutine
一起来学习。代码如下所示:
var (
numberChan chan int
)
numberChan = make(chan int)
go func() {
for i := 0; i < 100; i++ {
fmt.Printf("写入数据: %d\n", i)
numberChan <- i
time.Sleep(time.Duration(1) * time.Second)
}
}()
go func() {
for number := range numberChan {
fmt.Printf("读取数据: %d\n", number)
}
}()
select {}
在上面的代码中,我们首先声明了一个无缓冲的通道numberChan
,需要注意的是,channel
变量的声明与之前章节学习的map
是一致的。
之后我们使用go func
开启了一个写数据的协程,向通道中写入数据使用的是:numberChan <- i
,也就是符号:<-
。
同时我们开启了一个读取数据的协程,从通道中读取数据,我们这里使用了for range
读取,这就表示一直不停的读取。
如果我们只是单次读取,那么我们可以这样:
number := <-numberChan
在上面的示例中,我们用的是无缓冲的通道,因为我们没有定义通道的大小,那么有缓冲的通道应该如何使用呢?如下代码所示:
var (
numberBufferChan chan int
)
numberBufferChan = make(chan int, 5)
与之前所不同的是,我们在声明变量,使用make
的时候,我们增加了一个数字5
,这就表示通道的缓冲长度为5
。
也就是说,如果没有协程在读取的时候,我们最多可以向通道中写入5
个数据。
需要注意的是,当通道使用完成之后,我们需要进行关闭。如下代码所示:
close(numberBufferChan)
在上面的代码中,我们使用close()
对通道进行了关闭,通道关闭后读取端将读取不到数据。
截止目前,我们的程序是可以正常运行的,但是会发现在关闭通道后,可能会出现报错。
那么我们增加了另一个概念:WaitGroup
,这是Go
语言中用于管理一组协程的东西。
它会等待所有协程执行完毕之后才会进行退出,同时还可以实现协程的优雅退出。更新代码如下:
var (
wg sync.WaitGroup
numberChan chan int
)
numberChan = make(chan int)
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
fmt.Printf("写入数据: %d\n", i)
numberChan <- i
time.Sleep(time.Duration(1) * time.Second)
}
close(numberChan)
}()
wg.Add(1)
go func() {
defer wg.Done()
for number := range numberChan {
fmt.Printf("读取数据: %d\n", number)
}
}()
var (
numberBufferChan chan int
)
numberBufferChan = make(chan int, 5)
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
fmt.Printf("写入数据Buffer: %d\n", i)
numberBufferChan <- i
time.Sleep(time.Duration(1) * time.Second)
}
close(numberBufferChan)
}()
wg.Add(1)
go func() {
defer wg.Done()
for number := range numberBufferChan {
fmt.Printf("读取数据Buffer: %d\n", number)
}
}()
wg.Wait()
小结
channel
是实现并发的重要组成部分,使用channel
我们可以更加安全的进行并发编程。
关于本节总结如下:
channel
用于协程之间的数据同步分为有缓冲通道与无缓冲通道
无缓冲通道在没有读取时会阻塞
必须使用
make
进行通道的初始化使用
chan <- i
进行通道数据的发送通道传递的数据可以是任意类型的
使用
close()
对通道进行关闭